﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Notoric.Model.Utils
{
    /// <summary>
    /// Groups extension methods.
    /// </summary>
    public static class Extensions
    {
        /// <summary>
        /// Invokes a transform function on each element of a given sequence and returns
        /// the first element for which this value is maximal.
        /// </summary>
        /// <typeparam name="T">Type of elements in the sequence</typeparam>
        /// <param name="source">
        /// <para>
        /// Type: <see cref="IEnumerable{T}"/> of <typeparamref name="T"/>
        /// </para>
        /// <para>
        /// A sequence to determine the maximal element of.
        /// </para>
        /// </param>
        /// <param name="selector">
        /// <para>
        /// Type: <see cref="Func{T, TResult}"/> of <typeparamref name="T"/>, <see cref="IComparable"/>
        /// </para>
        /// <para>
        /// A transform function to apply on each element.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <typeparamref name="T"/>
        /// </para>
        /// <para>
        /// The first element in the sequence with the maximal value of the transform function.
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="source"/> or <paramref name="selector"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="InvalidOperationException">
        /// The <paramref name="source"/> contains no elements.
        /// </exception>
        public static T TakeMax<T>(this IEnumerable<T> source, Func<T, IComparable> selector)
        {
            if (selector == null)
                throw new ArgumentNullException("selector");

            T actualMax = source.First();
            IComparable maxVal = selector(actualMax);

            foreach (var item in source)
            {
                var val = selector(item);
                if (val.CompareTo(maxVal) > 0)
                {
                    actualMax = item;
                    maxVal = val;
                }
            }

            return actualMax;
        }

        /// <summary>
        /// Invokes a transform function on each element of a given sequence and returns
        /// the first element for which this value is minimal.
        /// </summary>
        /// <typeparam name="T">Type of elements in the sequence</typeparam>
        /// <param name="source">
        /// <para>
        /// Type: <see cref="IEnumerable{T}"/> of <typeparamref name="T"/>
        /// </para>
        /// <para>
        /// A sequence to determine the minimal element of.
        /// </para>
        /// </param>
        /// <param name="selector">
        /// <para>
        /// Type: <see cref="Func{T, TResult}"/> of <typeparamref name="T"/>, <see cref="IComparable"/>
        /// </para>
        /// <para>
        /// A transform function to apply on each element.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <typeparamref name="T"/>
        /// </para>
        /// <para>
        /// The first element in the sequence with the minimal value of the transform function.
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="source"/> or <paramref name="selector"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="InvalidOperationException">
        /// The <paramref name="source"/> contains no elements.
        /// </exception>
        public static T TakeMin<T>(this IEnumerable<T> source, Func<T, IComparable> selector)
        {
            T actualMin = source.First();
            IComparable minVal = selector(actualMin);

            foreach (var item in source)
            {
                var val = selector(item);
                if (val.CompareTo(minVal) < 0)
                {
                    actualMin = item;
                    minVal = val;
                }
            }

            return actualMin;
        }

        /// <summary>
        /// Invokes a transform function on each element of a given sequence.
        /// </summary>
        /// <typeparam name="T">Type of the elemnts in the sequence</typeparam>
        /// <param name="source">
        /// <para>
        /// Type: <see cref="IEnumerable{T}"/> of <typeparamref name="T"/>
        /// </para>
        /// <para>
        /// A sequence of elements to transform.
        /// </para>
        /// </param>
        /// <param name="transformer">
        /// <para>
        /// Type: <see cref="Action{T}"/> of <typeparamref name="T"/>
        /// </para>
        /// <para>
        /// A transform function.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="source"/> or <paramref name="transformer"/> is <see langword="null"/>.
        /// </exception>
        public static void Transform<T>(this IEnumerable<T> source, Action<T> transformer)
        {
            if (source == null)
                throw new ArgumentNullException("source");
            if (transformer == null)
                throw new ArgumentNullException("transformer");

            foreach (var item in source)
                transformer(item);
        }
    }
}
